﻿/*
 * XBMC Connect - A .Net library to remotely connect to XMBC
 * Copyright (C) 2010 - 2011 Pieter De Rycke
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Reflection;

namespace JsonRpc.Generator
{
    public class CommandLineParser
    {
        private const string DefaultArgumentName = "";

        private Dictionary<Type, IValueConverter> valueConverters;

        private Regex argumentWithValueRegex = new Regex(@"/(?<name>\w+):(?<value>.*)");
        private Regex argumentWithOutValueRegex = new Regex(@"/(?<name>\w+)");

        private Dictionary<string, string> argumentsWithValue;

        public CommandLineParser(string[] args)
        {
            valueConverters = new Dictionary<Type, IValueConverter>();
            valueConverters.Add(typeof(string), new StringValueConverter());
            valueConverters.Add(typeof(int), new IntegerValueConverter());
            valueConverters.Add(typeof(bool), new BoolValueConverter());

            argumentsWithValue = new Dictionary<string, string>();

            ParseArguments(args);
        }

        public void Fill(object o)
        {
            Type type = o.GetType();
            IEnumerable<PropertyInfo> properties = GetMarkedProperties(type);

            // Validate that all the found properties have supported types
            PropertyInfo unsupportedProperty = GetFirstUnsupportedProperty(properties);
            if (unsupportedProperty != null)
            {
                string argumentName = unsupportedProperty.Name;
                string message = string.Format("The property \"{0}\" marked with the CommandLineArgumentAttribute" + 
                    "is of an unsupported type.", argumentName);
                throw new CommandLineArgumentException(argumentName, message);
            }

            foreach (PropertyInfo property in properties)
            {
                CommandLineArgumentAttribute argumentAttribute = GetCommandLineArgumentAttribute(property);

                string value = GetArgumentValue(argumentAttribute, property.Name);

                if (value != null)
                {
                    // Set the value for the property
                    Type propertyType = property.PropertyType;
                    IValueConverter converter = valueConverters[propertyType];
                    property.SetValue(o, converter.Convert(value), null);
                }
            }
        }

        public string this[string argumentName]
        {
            get
            {
                return argumentsWithValue[argumentName];
            }
        }

        #region Util methods

        private void ParseArguments(string[] args)
        {
            if (args.Length > 0)
            {
                argumentsWithValue.Add(DefaultArgumentName, args[args.Length - 1]);

                for (int i = 0; i < args.Length - 1; i++)
                {
                    string argument = args[i];

                    TryParseArgument(argument);
                }
            }
        }

        private void TryParseArgument(string argument)
        {
            Match m = argumentWithValueRegex.Match(argument);
            if (m.Success)
            {
                string name = m.Groups["name"].Value;
                string value = m.Groups["value"].Value;

                StoreArgument(name, value);
            }
            else
            {
                m = argumentWithOutValueRegex.Match(argument);
                if (m.Success)
                {
                    string name = m.Groups["name"].Value;

                    StoreArgument(name);
                }
            }
        }

        private void StoreArgument(string argumentName, string argumentValue)
        {
            if (!argumentsWithValue.ContainsKey(argumentName))
                argumentsWithValue.Add(argumentName, argumentValue);
            else
                argumentsWithValue[argumentName] = argumentValue;
        }

        private void StoreArgument(string argumentName)
        {
            if (!argumentsWithValue.ContainsKey(argumentName))
                argumentsWithValue.Add(argumentName, "true");
        }

        private string GetArgumentValue(CommandLineArgumentAttribute argumentAttribute, string propertyName)
        {
            string argumentName;

            // Determine the name of the argument in the dictionary
            if (argumentAttribute.IsDefaultArgument)
                argumentName = DefaultArgumentName;
            else if (argumentAttribute.Name != null)
                argumentName = argumentAttribute.Name;
            else if (argumentAttribute.ShortName != null)
                argumentName = argumentAttribute.ShortName;
            else
                argumentName = propertyName;

            // Search the value in the dictionary
            if (argumentsWithValue.ContainsKey(argumentName))
                return argumentsWithValue[argumentName];
            else
                return null;
        }

        private IEnumerable<PropertyInfo> GetMarkedProperties(Type type)
        {
            return from p in type.GetProperties()
                   where p.GetCustomAttributes(typeof(CommandLineArgumentAttribute), false).Length > 0
                   select p;
        }

        private CommandLineArgumentAttribute GetCommandLineArgumentAttribute(PropertyInfo property)
        { 
            return (CommandLineArgumentAttribute)(property.GetCustomAttributes(typeof(CommandLineArgumentAttribute), false)[0]);
        }

        private PropertyInfo GetFirstUnsupportedProperty(IEnumerable<PropertyInfo> properties)
        {
            var query = from p in properties
                        where !IsSupportedPropertyType(p.PropertyType)
                        select p;
            return query.FirstOrDefault();
        }

        private bool IsSupportedPropertyType(Type propertyType)
        {
            return valueConverters.Keys.Contains(propertyType);
        }

        #endregion
    }
}
